<template>
  <div class="timeline-story" ref="wrapper" @mouseenter="isHover = true" @mouseleave="isHover = false">
    <div class="story__sticky-bar d-md-none" ref="tab">
      <div class="story__sticky-bar__content" ref="stickybar">
        <TimelineStoryStickybar
          :list="modelValue?.data"
          :currentIdx="activeIdx"
          @updateCurrentIdx="scrollToIdx"
        />
      </div>
    </div>
    <ul class="story__list" ref="listRef">
      <li
        class="story__item"
        v-for="(item, idx) in modelValue?.data"
        :key="item.id"
        :ref="(el: HTMLElement) => { if (el) rowRefs.push(el as HTMLElement) }"
        :class="{ 'is-active': activeIdx == idx }"
        :id="`${item.id}`"
      >
        <div class="story__content container-content">
          <div class="stroy__left">
            <div class="story__label" :ref="(el: HTMLElement) => { if (el) labelRefs.push(el as HTMLElement) }">
              <div class="story__label__title">
                {{ item.title }}
              </div>
              <div
                class="story__label__desc"
                v-html="item.desc"
              ></div>
            </div>
          </div>
          <div class="stroy__right">
            <div
              class="story__gallery"
              :class="{ 'is-active': activeIdx == idx }"
            >
              <TimelineStoryGallery
                :gallery="item.gallery"
                @openCaption="openCaption"
              />
            </div>
          </div>
        </div>
      </li>
      <div class="story__mask" ref="bg"></div>
      <div
        class="story__line"
        :style="[
          { '--height': `${lineHeight}px` },
          { '--bottom': `${lineBottomPercent * 100}%` },
          { '--space': `${lineSpacePercent * 100}%` },
        ]"
      ></div>
      <div class="story__dot" ref="dot"></div>
      <div class="story__mask story__mask--front" ref="bg2">
        <div class="story__scrollspy">
          <TimelineStoryScrollspy
            :list="modelValue?.data"
            :activeIdx="activeIdx"
            @scrollToIdx="scrollToIdx"
          />
        </div>
      </div>
    </ul>
    <transition name="fade-right">
      <TimelineStoryCaption
        :text="captionText"
        :image="captionImage"
        v-show="isCaptionOpen"
        @closeCaption="closeCaption"
      />
    </transition>

    <Operate v-if="!isPreview" v-model:show="isHover" @handle-edit="showSetting = true" @handle-delete="handleDelete" @handle-copy="handleCopy" @handle-paste="handlePaste"></Operate>
  </div>

  <Setting v-if="!isPreview" v-model:show="showSetting" :data="modelValue"></Setting>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
import TimelineStoryGallery from './TimelineStoryGallery.vue'
import TimelineStoryStickybar from './TimelineStoryStickybar.vue'
import TimelineStoryCaption from './TimelineStoryCaption.vue'
import TimelineStoryScrollspy from './TimelineStoryScrollspy.vue'
import Setting from './setting.vue'
import Operate from '@/common/Operate/index.vue'
import { randomString } from '@/utils/common'

// 定义组件名称
defineOptions({
  name: 'TimelineStory'
})

const props = defineProps({
  modelValue: {
    type: Object,
    default: () => {}
  },

  // 是否预览
  isPreview: {
    type: Boolean,
    default: true
  }
})

const emit = defineEmits(['update:modelValue', 'handleDelete', 'handleCopy', 'handlePaste'])

const handleInit = () => {
  if (props.modelValue && props.modelValue.data) return // 有数据不用初始化
  const data = {
    id: props.modelValue?.id || randomString(),
    type: 'TimelineStory',
    data: [
      {
        id: randomString(),
        title: '标题',
        desc: '描述',
        gallery: [
          {
            id: randomString(),
            imgSrc: 'https://osswebsite-test.cedim.cn/media-library/ywies-bj/images/home/ywies-tx.jpg',
          },
          {
            id: randomString(),
            imgSrc: 'https://osswebsite-test.cedim.cn/media-library/ywies-tx/images/campus-life/school-facilities/05_panoramic01_origin.jpg',
          }
        ]
      },
      {
        id: randomString(),
        title: '标题',
        desc: '描述',
        gallery: [
          {
            id: randomString(),
            imgSrc: 'https://osswebsite-test.cedim.cn/media-library/ywies-bj/images/home/ywies-tx.jpg',
          },
        ]
      },
    ]
  }
  emit('update:modelValue', data)
} 

const isHover = ref<boolean>(false);
const showSetting = ref<boolean>(false);

const handleDelete = () => {
  emit('handleDelete', props.modelValue.id)
}

const handleCopy = () => {
  emit('handleCopy', props.modelValue)
}

const handlePaste = (type: string) => {
  emit('handlePaste', type)
}

handleInit()

// 响应式数据
const activeIdx = ref(0)
const lineHeight = ref(0)
const lineBottomPercent = ref(0)
const lineSpacePercent = ref(0)
const captionText = ref('')
const captionImage = ref('')
const isCaptionOpen = ref(false)

// 滚动相关状态
const vsWidth = ref(typeof window === 'undefined' ? 1024 : window.innerWidth)
const vsScrollY = ref(typeof window === 'undefined' ? 0 : window.scrollY)
const isScrollDown = ref(false)

// 模板引用
const wrapper = ref<HTMLElement | null>(null)
const tab = ref<HTMLElement | null>(null)
const stickybar = ref<HTMLElement | null>(null)
const listRef = ref<HTMLElement | null>(null)
const bg = ref<HTMLElement | null>(null)
const bg2 = ref<HTMLElement | null>(null)
const dot = ref<HTMLElement | null>(null)
const rowRefs = ref<HTMLElement[]>([])
const labelRefs = ref<HTMLElement[]>([])

// 计算属性
const headerHeight = computed(() => {
  return vsWidth.value > 1023.98 ? 100 : 66
})

const isNavHide = computed(() => {
  return isScrollDown.value
})

// 监听器
watch(vsWidth, () => {
  closeCaption()
})
// 工具函数
const getPositionY = (element: HTMLElement | null) => {
  if (!element) return 0
  const scrollY = typeof window === 'undefined' ? 0 : window.scrollY
  return element.getBoundingClientRect().top + scrollY
}

const getPosition = (element: HTMLElement | null) => {
  if (!element) return { x: 0, y: 0 }
  const rect = element.getBoundingClientRect()
  const scrollX = typeof window === 'undefined' ? 0 : window.scrollX
  const scrollY = typeof window === 'undefined' ? 0 : window.scrollY
  return {
    x: rect.left + scrollX,
    y: rect.top + scrollY
  }
}

const bodyScrollHandler = (disable: boolean) => {
  if (typeof document === 'undefined') return
  if (disable) {
    document.body.style.overflow = 'hidden'
  } else {
    document.body.style.overflow = ''
  }
}

// 事件处理函数
const onEvent = () => {
  if (typeof window === 'undefined') return
  window.addEventListener('scroll', onLabelTrigger)
  window.addEventListener('resize', updateViewportSize)
  updateViewportSize()
}

const removeEvent = () => {
  if (typeof window === 'undefined') return
  window.removeEventListener('scroll', onLabelTrigger)
  window.removeEventListener('resize', updateViewportSize)
}

const updateViewportSize = () => {
  if (typeof window === 'undefined') return
  vsWidth.value = window.innerWidth
  vsScrollY.value = window.scrollY
}

const onLabelTrigger = () => {
  if (typeof window !== 'undefined') {
    vsScrollY.value = window.scrollY
  }
  const scanLine = vsScrollY.value + headerHeight.value + 64
  const listTop = getPositionY(listRef.value)
  const listBottom = listTop + (listRef.value?.clientHeight || 0)

  if (scanLine < listTop) {
    activeIdx.value = 0
  } else if (scanLine > listBottom) {
    activeIdx.value = 999
  }

  bgHandler()
  dotHandler()
  lineHandler()
  labelHandler()
  indexHandler()
  checkStickybarFixed()
}
const bgHandler = () => {
  const scanLine = vsScrollY.value + headerHeight.value + 64
  const listTop = getPositionY(listRef.value)
  const listBottom = listTop + (listRef.value?.clientHeight || 0)

  const bound = {
    top: listTop + 15,
    bottom: listBottom - 378,
  }

  const targets = [bg.value, bg2.value]
  if (!bg.value || !bg2.value) return

  if (scanLine < bound.top) {
    targets.forEach((el) => {
      el?.classList.add('is-top')
      el?.classList.remove('is-fixed')
      el?.classList.remove('is-bottom')
    })
  } else if (scanLine > bound.bottom) {
    targets.forEach((el) => {
      el?.classList.add('is-bottom')
      el?.classList.remove('is-fixed')
      el?.classList.remove('is-top')
    })
  } else {
    targets.forEach((el) => {
      el?.classList.add('is-fixed')
      el?.classList.remove('is-top')
      el?.classList.remove('is-bottom')
    })
  }
}
const dotHandler = () => {
  const scanLine = vsScrollY.value + headerHeight.value + 64
  const target = dot.value
  const listTop = getPositionY(listRef.value)
  const listBottom = listTop + (listRef.value?.clientHeight || 0)

  const bound = {
    top: listTop - 80,
    bottom: listBottom - 181,
  }

  if (!target) return

  if (scanLine < bound.top) {
    target.classList.add('is-top')
    target.classList.remove('is-fixed')
    target.classList.remove('is-bottom')
  } else if (scanLine > bound.bottom) {
    target.classList.add('is-bottom')
    target.classList.remove('is-fixed')
    target.classList.remove('is-top')
  } else {
    target.classList.add('is-fixed')
    target.classList.remove('is-top')
    target.classList.remove('is-bottom')
  }
}
const lineHandler = () => {
  const listTop = getPositionY(listRef.value)
  const dotTop = getPositionY(dot.value)
  lineHeight.value = listRef.value?.clientHeight || 0
  lineSpacePercent.value = 41 / lineHeight.value
  const bluLineLength = dotTop < listTop
    ? vsScrollY.value + dotTop - listTop
    : dotTop - listTop
  lineBottomPercent.value = (bluLineLength + 12) / lineHeight.value
}
const labelHandler = () => {
  const scanLine = vsScrollY.value + headerHeight.value + 64
  const len = rowRefs.value.length

  for (let i = 0; i < len; i++) {
    const row = rowRefs.value[i]
    const rowTop = getPositionY(row)
    const rowHeight = row.clientHeight
    const rowBottom = getPositionY(row) + rowHeight
    const label = labelRefs.value[i]
    const labelHeight = label.clientHeight

    const bound = isNavHide.value
      ? {
          top: rowTop + 31,
          bottom: rowBottom - labelHeight,
        }
      : {
          top: rowTop - 10,
          bottom: rowBottom - labelHeight - 30,
        }

    if (rowHeight < labelHeight) {
      label.classList.add('is-top')
      label.classList.remove('is-fixed')
      label.classList.remove('is-bottom')
      continue
    }

    if (scanLine < bound.top) {
      label.classList.add('is-top')
      label.classList.remove('is-fixed')
      label.classList.remove('is-bottom')
    } else if (scanLine > bound.bottom) {
      label.classList.add('is-bottom')
      label.classList.remove('is-fixed')
      label.classList.remove('is-top')
    } else {
      label.classList.add('is-fixed')
      label.classList.remove('is-top')
      label.classList.remove('is-bottom')
    }
  }
}

const indexHandler = () => {
  const scanLine = vsScrollY.value + headerHeight.value + 64
  const len = rowRefs.value.length
  let foundActive = false

  for (let i = 0; i < len; i++) {
    const row = rowRefs.value[i]
    const rowTop = getPositionY(row)
    const rowBottom = getPositionY(row) + row.clientHeight
    const label = labelRefs.value[i]
    const labelHeight = label.clientHeight
    const bound = {
      top: rowTop - 300,
      bottom: rowBottom - labelHeight,
    }

    if (scanLine > bound.top && scanLine < bound.bottom) {
      activeIdx.value = i
      foundActive = true
      break
    }
  }

  // 如果没有找到激活的项，检查是否在第一个项之前
  if (!foundActive && len > 0) {
    const firstRow = rowRefs.value[0]
    const firstRowTop = getPositionY(firstRow)
    if (scanLine < firstRowTop - 300) {
      activeIdx.value = 0
    }
  }
}

const scrollToIdx = (idx: number) => {
  const fix = vsWidth.value < 768 ? 120 : 150
  const targetTop = getPosition(rowRefs.value[idx]).y - fix
  if (typeof window !== 'undefined') {
    window.scroll({ top: targetTop, left: 0, behavior: 'smooth' })
  }
}

const checkStickybarFixed = () => {
  const wrapperEl = wrapper.value
  if (!wrapperEl) return

  const wrapperTop = wrapperEl.getBoundingClientRect().top
  const wrapperBottom = wrapperEl.getBoundingClientRect().bottom
  const headerHeight = (typeof window === 'undefined' || typeof document === 'undefined')
    ? (vsWidth.value < 768 ? 66 : 100)
    : Number(
        window.getComputedStyle(document.body)
          .getPropertyValue('--header-height-calc')
          .split('px')[0]
      )

  const target = stickybar.value
  if (!target) return

  if (wrapperTop - 80 > headerHeight) {
    target.classList.remove('is-fixed')
    target.classList.add('d-none')
  } else if (wrapperBottom < headerHeight) {
    target.classList.remove('is-fixed')
    target.classList.add('d-none')
  } else {
    target.classList.add('is-fixed')
    target.classList.remove('d-none')
  }
}

const openCaption = (obj: { text: string; image: string }) => {
  captionText.value = obj.text
  captionImage.value = obj.image
  bodyScrollHandler(true)
  isCaptionOpen.value = true
}

const closeCaption = () => {
  bodyScrollHandler(false)
  isCaptionOpen.value = false
}

const setRowMinHeight = () => {
  const rows = rowRefs.value
  const len = rows.length

  for (let i = 0; i < len; i++) {
    const row = rowRefs.value[i]
    const rowHeight = row.clientHeight
    const label = labelRefs.value[i]
    const labelHeight = label.clientHeight
    const rowChild = row.children[0] as HTMLElement

    if (rowHeight < labelHeight) {
      rowChild.style.minHeight = `${labelHeight}px`
    }
  }
}

// 生命周期钩子
onMounted(() => {
  // 清空 refs 数组
  rowRefs.value = []
  labelRefs.value = []

  nextTick(() => {
    onEvent()
    onLabelTrigger()
    setRowMinHeight()
  })
})

onBeforeUnmount(() => {
  removeEvent()
})
</script>

<style lang="scss" scoped>
.d-md-none {
  display: none!important
}
.timeline-story {
  position: relative;
  --border-color: #0032a0;
  --dot-color: #0066cc;
  overflow: hidden;

  .story__sticky-bar {
    height: 48px;
    position: relative;
    z-index: 410;

    &__content {
      position: absolute;
      width: 100%;
      height: 100%;
      background-color: #f8f9fa;
      left: 50%;
      transform: translateX(-50%);
      box-shadow: 0px 1px 0px #eaeaea;

      &.is-fixed {
        height: 58px;
        position: fixed;
        padding-top: 10px;
        top: calc(var(--header-height-calc) + var(--alert-height) - 10px);
      }
    }
  }

  .story__list {
    position: relative;
    z-index: 1;

    .story__item {
      transition: all 0.3s ease-in-out;
      padding-top: 17px;
      padding-bottom: 14px;

      @media (max-width: 1120px) {
        padding-left: 65px;
      }

      @media (max-width: 767.98px) {
        padding-left: 0;
      }

      &:not(:first-of-type) {
        margin-top: 69px;

        @media (max-width: 767.98px) {
          margin-top: 50px;
        }
      }

      &.is-active {
        .story__content {
          .stroy__left {
            .story__label {
              opacity: 1;

              .story__label__title {
                color: #0066cc;

                @media (max-width: 767.98px) {
                  color: #0032a0;
                }
              }
            }
          }
        }
      }
    }
    .story__mask {
      z-index: 1;
      height: 378px;
      width: 100%;
      background-color: #fff;

      @media (max-width: 767.98px) {
        display: none;
      }

      &.is-fixed {
        position: fixed;
        top: 145px;
        bottom: initial;

        @media (max-width: 1023.98px) {
          top: 110px;
        }
      }

      &.is-top {
        position: absolute;
        top: 0;
      }

      &.is-bottom {
        position: absolute;
        top: initial;
        bottom: 0;
      }

      &--front {
        background: none;
        z-index: 2;
        pointer-events: none;
        height: 50vh;
      }
    }

    .story__line {
      position: absolute;
      top: 0;
      left: 50%;
      width: 1px;
      height: var(--height);
      z-index: 1;
      transform: translateX(calc(-50% - 76px));

      @media (max-width: 1120px) {
        left: 54%;
      }

      @media (max-width: 900px) {
        left: 58%;
      }

      @media (max-width: 800px) {
        left: 60%;
      }

      @media (max-width: 767.98px) {
        display: none;
      }

      &::before,
      &::after {
        content: '';
        position: absolute;
        width: 100%;
      }

      &::before {
        height: calc(100% - var(--space) * 2);
        top: var(--space);
        background-image: linear-gradient(180deg, #666 50%, rgba(255,255,255,0) 0%);
        background-position: right;
        background-size: 8px 16px;
        background-repeat: repeat-y;
        z-index: -1;
      }

      &::after {
        height: 100%;
        background-image: linear-gradient(180deg, #0066cc 0%, #0066cc var(--bottom), rgba(255,255,255,0) var(--bottom), rgba(255,255,255,0) calc(100% - var(--space)), rgba(255,255,255,0) calc(100% - var(--space)), rgba(255,255,255,0) 100%);
        z-index: 1;
      }
    }

    .story__dot {
      position: absolute;
      width: 24px;
      height: 24px;
      border-radius: 50%;
      background-color: #0066cc;
      left: 50%;
      transform: translateX(calc(-50% - 76px));
      z-index: 2;

      @media (max-width: 1120px) {
        left: 54%;
      }

      @media (max-width: 900px) {
        left: 58%;
      }

      @media (max-width: 800px) {
        left: 60%;
      }

      @media (max-width: 767.98px) {
        display: none;
      }

      &.is-fixed {
        position: fixed;
        top: 285px;
        bottom: initial;

        @media (max-width: 1023.98px) {
          top: 250px;
        }
      }

      &.is-top {
        position: absolute;
        top: 41px;
      }

      &.is-bottom {
        position: absolute;
        top: initial;
        bottom: 41px;
      }
    }

    .story__scrollspy {
      position: absolute;
      left: -1px;
      top: 40%;
      transform: translateY(-50%);
      pointer-events: visible;
    }
  }
  .story__content {
    display: flex;
    justify-content: space-between;
    position: relative;
    z-index: 2;

    @media (max-width: 767.98px) {
      flex-direction: column;
    }

    .stroy__left {
      flex: 1 0 300px;
      max-width: 378px;
      position: relative;

      @media (max-width: 900px) {
        flex: 1 0 270px;
        max-width: 270px;
      }

      @media (max-width: 767.98px) {
        flex: 0 0 auto;
        max-width: 100%;
        margin-bottom: 16px;
      }

      .story__label {
        max-width: 350px;
        opacity: 0.6;

        @media (max-width: 1023.98px) {
          max-width: 300px;
        }

        @media (max-width: 767.98px) {
          opacity: 1;
          position: static;
          max-width: 100%;
        }

        &.is-fixed {
          position: fixed;
          top: calc(var(--header-height-calc) + 100px);
          bottom: initial;

          @media (max-width: 1023.98px) {
            top: calc(var(--header-height-calc) + 65px);
          }

          @media (max-width: 767.98px) {
            position: static;
          }
        }

        &.is-top {
          position: absolute;
          top: 0;

          @media (max-width: 767.98px) {
            position: static;
          }
        }

        &.is-bottom {
          position: absolute;
          top: initial;
          bottom: 0;

          @media (max-width: 767.98px) {
            position: static;
          }
        }

        &__title {
          color: #64788f;
          font-size: 80px;
          line-height: 1;
          letter-spacing: 0.02em;
          font-weight: bold;
          transition: color 0.3s ease-in-out;

          @media (max-width: 767.98px) {
            color: #0032a0;
            font-size: 58px;
            line-height: 0.689em;
            letter-spacing: 0.015em;
          }
        }

        &__desc,
        &__desc :deep(p) {
          font-size: 16px;
          line-height: 1.5;
          color: #1a1c20;
        }

        &__desc :deep(a) {
          color: #0066cc;
          text-decoration: underline;
        }

        &__desc {
          margin-top: 24px;

          @media (max-width: 767.98px) {
            margin-top: 12px;
          }
        }
      }
    }

    .stroy__right {
      width: 480px;

      @media (max-width: 1023.98px) {
        width: 440px;
      }

      @media (max-width: 900px) {
        width: 370px;
      }

      @media (max-width: 800px) {
        width: 320px;
      }

      @media (max-width: 767.98px) {
        width: 100vw;
        margin-left: -1rem;
      }

      .story__gallery {
        filter: grayscale(100%);
        opacity: 0.6;
        transition: filter 0.3s ease-in-out, opacity 0.3s ease-in-out;

        @media (max-width: 767.98px) {
          filter: grayscale(0%);
          opacity: 1;
        }

        &.is-active {
          filter: grayscale(0%);
          opacity: 1;
        }
      }
    }
  }

  .fade-right-enter-active,
  .fade-right-leave-active {
    transition: transform 0.25s ease-out, opacity 0.25s ease-out;
  }

  .fade-right-enter,
  .fade-right-leave-to {
    transform: translateX(101%);
    opacity: 0;
  }

  .fade-right-enter-to,
  .fade-right-leave {
    transform: translateX(0%);
    opacity: 1;
  }
}
</style>